﻿using System;
using System.Linq;

namespace Picnic.Spec.Specifications
{
	public class SpecificationSpec : Specification
	{
		protected override void SpecificationBody()
		{
			Describe<Specification>("instance", () =>
			{
				#region Initialization

				var scopes = (new RightSpec()).GetStatementScopes().ToArray();
				var statements = scopes.SelectMany(scope => scope.Statements).ToArray();

				#endregion

				It("should contians all described statement scopes", () =>
				{
					scopes.Length.ShouldEquals(3);

					scopes[0].Subject.ShouldEquals("SCOPE_1");
					scopes[0].Context.ShouldEquals("CONTEXT_1");

					scopes[1].Subject.ShouldEquals("SCOPE_2");
					scopes[1].Context.ShouldEquals("CONTEXT_2");

					scopes[2].Subject.ShouldEquals("SCOPE_3");
					scopes[2].Context.ShouldEquals("CONTEXT_3");
				});

				It("should contians all described statements", () =>
				{
					statements.Length.ShouldEquals(6);
					statements[0].Text.ShouldEquals("STATEMENT_1_1");
					statements[1].Text.ShouldEquals("STATEMENT_1_2");
					statements[2].Text.ShouldEquals("STATEMENT_1_3");
					statements[3].Text.ShouldEquals("STATEMENT_2_1");
					statements[4].Text.ShouldEquals("STATEMENT_2_2");
					statements[5].Text.ShouldEquals("STATEMENT_3_1");
				});

				It("should not contains nested statement scopes", () =>
				{
					var spec = new SpecWithNestedScope();
					((Action)(() => spec.GetStatementScopes())).ShouldRaise<SpecificationException>();
				});

				It("should not contains statements declared out of scope", () =>
				{
					var spec = new SpecWithOutOfScopeStatement();
					((Action)(() => spec.GetStatementScopes())).ShouldRaise<SpecificationException>();
				});

				It("should not contains before actions declared out of scope", () =>
				{
					var spec = new SpecWithOutOfScopeBeforeAction();
					((Action)(() => spec.GetStatementScopes())).ShouldRaise<SpecificationException>();
				});

				It("should not contains more then one before action declaration in scope", () =>
				{
					var spec = new SpecWithTwoBeforeActions();
					((Action)(() => spec.GetStatementScopes())).ShouldRaise<SpecificationException>();
				});
			});
		}

		private class RightSpec : Specification
		{
			protected override void SpecificationBody()
			{
				Describe("SCOPE_1", "CONTEXT_1", () =>
				{
					It("STATEMENT_1_1", Pass);
					It("STATEMENT_1_2", Pass);
					It("STATEMENT_1_3", Pass);
				});

				Describe("SCOPE_2", "CONTEXT_2", () =>
				{
					It("STATEMENT_2_1", Pass);
					It("STATEMENT_2_2", Pass);
				});

				Describe("SCOPE_3", "CONTEXT_3", () => It("STATEMENT_3_1", Pass));
			}
		}

		private class SpecWithNestedScope : Specification
		{
			protected override void SpecificationBody()
			{
				Describe("Scope", string.Empty, () =>
					Describe("Nested scope", string.Empty, () =>
					{
					}));
			}
		}

		private class SpecWithOutOfScopeStatement : Specification
		{
			protected override void SpecificationBody()
			{
				It("should raise out of scope exception", Pass);
			}
		}

		private class SpecWithOutOfScopeBeforeAction : Specification
		{
			protected override void SpecificationBody()
			{
				Before(() => { });
			}
		}

		private class SpecWithTwoBeforeActions : Specification
		{
			protected override void SpecificationBody()
			{
				Before(() => { });
				Before(() => { });
			}
		}
	}
}